#include <cmath>
#include "ft2build.h"
#include FT_FREETYPE_H
#include <freetype/ftglyph.h>

#include <iostream>

#include "core.h"
#include "font.h"
#include "time.h"

// Code adapted from http://nehe.gamedev.net/tutorial/freetype_fonts_in_opengl/24001/

namespace {
    FT_Library ft_library = 0;
    
    int next_power_of_2(int a) {
        int p = 1;
        while (p < a) p *= 2;
        return p;
    }
}  // namespace

namespace font {

    struct Char {
        GLuint texture;
        int tw, th;
        int w, h;
        double left, bottom;
        double advance;
    };
    
    void Init() {
        CHECK(!FT_Init_FreeType(&ft_library));
    }

    struct FontImpl {
        double height_;
        FontImpl(double size) : height_(size) {
            for (int i = 0; i < 128; ++i) {
                chars[i].texture = 0;
            }
        }
        ~FontImpl() {
            for (int i = 0; i < 128; ++i) {
                if (chars[i].texture) {
                    glDeleteTextures(1, &chars[i].texture);
                }
            }
        }

        double DrawString(bool draw, const std::string &text, const std::vector<u32> &colors, double jiggle_size, double jiggle_frequency, double advance) {
            const char *p = text.c_str();
            double x = 0, y = 0;
            int col_idx = 0;
            for (int i = 0; p[i]; ++i) {
                int c = CLAMP(p[i], 0, 127);
                if (!chars[c].texture) continue;
                double tx = chars[c].w * 1.0 / chars[c].tw;
                double ty = chars[c].h * 1.0 / chars[c].th;
                double x1 = x + chars[c].left;
                double y1 = y + chars[c].bottom;
                if (draw) {
                    if (colors.size() == 0) {
                        SetColor(COL_BLACK);
                    } else {
                        SetColor(colors[col_idx % colors.size()]);
                        if (c != ' ') ++col_idx;
                    }
                    double dy = 0;
                    if (jiggle_size) {
                        double theta = fmod(gametime::GlobalTime() * jiggle_frequency, 1.0);
                        dy = jiggle_size * cos(i + theta * CONST_TWOPI);
                    }
                    glBindTexture(GL_TEXTURE_2D, chars[c].texture);
                    glBegin(GL_QUADS);
                    glTexCoord2d(0, 0); glVertex2f(int(x1), dy + y1 + chars[c].h);
                    glTexCoord2d(0, ty); glVertex2f(int(x1), dy + y1);
                    glTexCoord2d(tx, ty); glVertex2f(int(x1 + chars[c].w), dy + y1);
                    glTexCoord2d(tx, 0); glVertex2f(int(x1 + chars[c].w), dy + y1 + chars[c].h);
                    glEnd();
                }
                x += advance != 0 ? advance : chars[c].advance;
            }
            return x;
        }

        void AddChar(FT_Face face, int c) {
            CHECK(!FT_Load_Glyph(face, FT_Get_Char_Index(face, c), FT_LOAD_DEFAULT));
            FT_Glyph glyph;
            CHECK(!FT_Get_Glyph(face->glyph, &glyph));
            FT_Glyph_To_Bitmap(&glyph, ft_render_mode_normal, 0, 1);
            FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;
            FT_Bitmap &bitmap = bitmap_glyph->bitmap;
            int width = next_power_of_2(bitmap.width);
            int height = next_power_of_2(bitmap.rows);
            GLubyte *data = new GLubyte[2 * width * height];
            for (int j = 0; j < height; ++j) {
                for (int i = 0; i < width; ++i) {
                    data[2 * (i + j * width)] = 255;
                    data[2 * (i + j * width) + 1] =
                        (i >= bitmap.width || j >= bitmap.rows) ?
                    0 : bitmap.buffer[i + bitmap.width * j];
                }
            }
            glGenTextures(1, &chars[c].texture);
            glBindTexture(GL_TEXTURE_2D, chars[c].texture);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
                         GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data);
            delete data;
            chars[c].tw = width;
            chars[c].th = height;
            chars[c].w = bitmap.width;
            chars[c].h = bitmap.rows;
            chars[c].left = bitmap_glyph->left;
            chars[c].bottom = bitmap_glyph->top - bitmap.rows;
            chars[c].advance = face->glyph->advance.x / 64.0;
        }
        Char chars[128];
    };
    
    Font::~Font() { delete impl_; }

    Font *Font::Load(const char* name, int size) {
        FT_Face face;
        std::string fname = resource_dir();
        fname = fname + "/";
        fname = fname + name;
        if (FT_New_Face(ft_library, fname.c_str(), 0, &face)) {
            std::cout << "font " << fname << " not loaded\n";
            return 0;
        }
        FT_Set_Char_Size(face, size * 64, size * 64, 96, 96);
        FontImpl *impl = new FontImpl(size);
        for (int i = 32; i < 128; i++) {
            impl->AddChar(face, i);
        }
        FT_Done_Face(face);
        return new Font(impl);
    }

    void Draw::push_state() {
        glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT);
        glDisable(GL_LIGHTING);
        glEnable(GL_TEXTURE_2D);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glPushMatrix();
        glTranslated(x_, y_, 0);
    }
    void Draw::pop_state() {
        glPopMatrix();
        glPopAttrib();
    }
    void Draw::draw(const std::string& text) {
        double dx = 0;
        if (center_) {
            dx = 0.5 * font_->impl_->DrawString(false, text, colors_, 0, 0, fixed_advance_);
        }
        x_ -= dx;
        if (shadow_) {
            std::vector<u32> shadow_colors;
            shadow_colors.push_back(RGBA(255, 255, 255, 64));
            int shadow_offset = 4;
            if (outline_.enabled) shadow_offset += 2;
            x_ -= shadow_offset;
            y_ -= shadow_offset;
            push_state();
            font_->impl_->DrawString(true, text, shadow_colors, jiggle_.size, jiggle_.frequency, fixed_advance_);
            pop_state();
            x_ += shadow_offset;
            y_ += shadow_offset;
        }
        if (outline_.enabled) {
            std::vector<u32> outline_colors;
            outline_colors.push_back(outline_.color);
            for (int xx = -2; xx <= 2; ++xx) {
                for (int yy = -2; yy <= 2; ++yy) {
                    x_ += xx;
                    y_ += yy;
                    push_state();
                    font_->impl_->DrawString(true, text, outline_colors, jiggle_.size, jiggle_.frequency, fixed_advance_);
                    pop_state();
                    x_ -= xx;
                    y_ -= yy;
                }
            }
        }
        push_state();
        font_->impl_->DrawString(true, text, colors_, jiggle_.size, jiggle_.frequency, fixed_advance_);
        pop_state();
        x_ += dx;
    }

    double Font::Height() { return impl_->height_; }
    
}  // namespace font
